using System.Collections.Generic;
using System.Diagnostics;

namespace NGridDrawer.Partitions
{
    /// <LastUpdate author="Blaise Braye" Date="15/06/2008 17:01"/>
    /// <summary>
    /// Represents a grid which is a set of <see cref="PartitionLevel"/> 
    /// and <see cref="PartitionColumn"/> objects.
    /// </summary>
    /// <remarks>
    /// a partitionLevel with a partitionColumn form a partitionElement together.
    /// <include file="Partitions.doc.xml" path="NAMESPACE/TYPE[@name=&quot;PartitionGrid&quot;]/remarks/*"/></remarks>
    public class PartitionGrid
    {
        private float _height;
        private float _width;


        private PartitionGrid()
        {
        }


        /// <summary>
        /// 	<para>Initializes an instance of the <see cref="PartitionGrid"/> class.</para>
        /// </summary>
        /// <param name="rowsHeight">heights of each row contained in the source data grid</param>
        /// <param name="columnsWidth">widths of each column contained in the source data grid</param>
        /// <param name="rowHeaderWidth">width of row header in the source data grid</param>
        /// <param name="partitionOrder">order in which partitionGrid must order partitions</param>
        /// <param name="clipHeights">clip heights of partition levels</param>
        /// <param name="clipWidths">clip widths of partition columns</param>
        /// <include file="Partitions.doc.xml" path="NAMESPACE/TYPE[@name=&quot;PartitionGrid&quot;]/*"/>
        public PartitionGrid(float[] rowsHeight, float[] columnsWidth,float rowHeaderWidth,
                             PartitionOrder partitionOrder,
                             float[] clipHeights, float[] clipWidths) : this()
        {
            this.rowsHeight = rowsHeight;
            this.columnsWidth = columnsWidth;
            this.rowHeaderWidth = rowHeaderWidth;
            PartitionOrder = partitionOrder;

            BuildPartition(clipHeights, clipWidths);
        }

        private PartitionColumn[] partitionColumns { get; set; }
        private PartitionLevel[] partitionLevels { get; set; }

        /// <summary>
        /// Gets the order in which partitionGrid must order partitions
        /// </summary>
        protected PartitionOrder PartitionOrder { get; set; }

        /// <summary>
        /// Represents the width of the columns which have to be printed indexed from 0
        /// </summary>
        private float[] columnsWidth { get; set; }

        /// <summary>
        /// Represents the widths of the columns which have to be printed indexed from 0
        /// </summary>
        private float[] rowsHeight { get; set; }


        /// <summary>
        /// Represents the width of row header column which have to be printed
        /// </summary>
        private float rowHeaderWidth { get; set; }


        /// <summary>
        /// Gets the width of the partitionGrid, 
        /// this value should equal the sum of the partition's columns' width
        /// </summary>
        public float Width
        {
            get { return _width; }
        }

        /// <summary>
        /// Gets the height of the partitionGrid, 
        /// this value should equal the sum of the partition's rows' width
        /// </summary>
        public float Height
        {
            get { return _height; }
        }

        /// <summary>
        /// Gets the total number of partition's columns
        /// </summary>
        public int PartitionColumnCount
        {
            get { return partitionColumns.Length; }
        }

        /// <summary>
        /// Gets the total number of partition's levels
        /// </summary>
        public int PartitionLevelCount
        {
            get { return partitionLevels.Length; }
        }

        /// <summary>
        /// Gets the partitionElement which is on top left in the partition grid
        /// </summary>
        public PartitionElement FirstElement
        {
            get { return new PartitionElement(this, 0, 0); }
        }

        /// <summary>
        /// Gets an element enumerator through whole of the partitionGrid
        /// </summary>
        public IEnumerator<PartitionElement> ElementEnumerator
        {
            get
            {
                PartitionElement element = FirstElement;
                do
                {
                    yield return element;
                } while ((element = NextElement(element)) != null);
            }
        }

        /// <summary>
        /// Build the partition from <paramref name="clipHeights"/> and <paramref name="clipWidths"/>
        /// </summary>
        /// <param name="clipHeights">clip heights of partition levels</param>
        /// <param name="clipWidths">clip widths of partition columns</param>
        public PartitionGrid BuildPartition(float[] clipHeights, float[] clipWidths)
        {
            BuildPartitionLevels(clipHeights);
            BuildPartitionColumns(clipWidths);

            return this;
        }

        /// <summary>
        /// Gets a partitionColumn
        /// </summary>
        /// <param name="index">index of the needed partitionColumn 
        /// (range between 0 and <see cref="PartitionColumnCount"/>-1)</param>
        /// <returns>a partitionColumn which has index property valued to <paramref name="index"/></returns>
        public PartitionColumn PartitionColumn(int index)
        {
            return partitionColumns[index];
        }

        /// <summary>
        /// Gets a partitionLevel
        /// </summary>
        /// <param name="index">index of the needed partitionLevel 
        /// (range between 0 and <see cref="PartitionLevelCount"/>-1)</param>
        /// <returns>a partitionLevel which has index property valued to <paramref name="index"/></returns>
        public PartitionLevel PartitionLevel(int index)
        {
            return partitionLevels[index];
        }


        /// <summary>
        /// Initialize partition levels
        /// </summary>
        /// <param name="clipHeights">defines the maximum bounds for height of each partitionLevel</param>
        /// <include file="Partitions.doc.xml" path="NAMESPACE/TYPE[@name=&quot;PartitionGrid&quot;]/MEMBER[@name=&quot;BuildPartitionLevels&quot;]/*"/>
        protected void BuildPartitionLevels(float[] clipHeights)
        {
            bool notfinished = (rowsHeight.Length > 0);
            _height = 0;
            if (notfinished)
            {
                #region partition grid rows

                int rowCounter = 0;
                int heightsCounter = 0;

                var partitionsRowIdList = new List<int>();
                var partitionsHeightList = new List<float>();


                float tmpH = rowsHeight[rowCounter];
                do
                {
                    partitionsRowIdList.Insert(0, rowCounter);
                    float height = 0;
                    do
                    {
                        height += tmpH;

                        if (++rowCounter < rowsHeight.Length)
                            tmpH = rowsHeight[rowCounter];
                    } while (rowCounter < rowsHeight.Length
                             && height + tmpH <= clipHeights[heightsCounter]);

                    partitionsHeightList.Insert(0, height);
                    _height += height;
                    if (heightsCounter + 1 < clipHeights.Length) heightsCounter++;
                } while (rowCounter < rowsHeight.Length);

                partitionsRowIdList.Reverse();
                partitionsHeightList.Reverse();

                #endregion

                #region initialize partitionLevels

                float[] partitionsHeight = partitionsHeightList.ToArray();
                int[] partitionsRowId = partitionsRowIdList.ToArray();

                partitionLevels = new PartitionLevel[partitionsRowId.Length];
                for (int i = 0; i < partitionsRowId.Length; i++)
                {
                    var level =
                        new PartitionLevel(
                            this, i,
                            partitionsRowId[i],
                            (i + 1 < partitionsRowId.Length) ? partitionsRowId[i + 1] - 1 : rowsHeight.Length - 1,
                            partitionsHeight[i]);
                    partitionLevels[i] = level;
                }

                #endregion
            }
        }

        /// <summary>
        /// Initialize partition columns
        /// </summary>
        /// <param name="clipWidths">define the maximum bounds for width of each partitionColumn</param>
        /// <include file="Partitions.doc.xml" path="NAMESPACE/TYPE[@name=&quot;PartitionGrid&quot;]/MEMBER[@name=&quot;BuildPartitionColumns&quot;]/*"/>
        protected void BuildPartitionColumns(float[] clipWidths)
        {
            bool notfinished = (columnsWidth.Length > 0);
            _width = 0;
            if (notfinished)
            {
                #region partition grid columns

                int colCounter = 0;
                int widthsCounter = 0;

                var partitionsColIdList = new List<int>();
                var partitionsWidthList = new List<float>();

                do
                {
                    partitionsColIdList.Insert(0, colCounter);
                    float width = rowHeaderWidth;
                    do
                    {
                        width += columnsWidth[colCounter];

                        notfinished = (++colCounter != columnsWidth.Length);
                    } while (notfinished
                             && width + columnsWidth[colCounter] <= clipWidths[widthsCounter]);

                    partitionsWidthList.Insert(0, width);
                    _width += width;

                    if (widthsCounter + 1 < clipWidths.Length) widthsCounter++;
                } while (notfinished);

                partitionsColIdList.Reverse();
                partitionsWidthList.Reverse();

                #endregion

                #region initialize partitionColumns

                float[] partitionsWidth = partitionsWidthList.ToArray();
                int[] partitionsColId = partitionsColIdList.ToArray();

                partitionColumns = new PartitionColumn[partitionsColId.Length];
                for (int i = 0; i < partitionsColId.Length; i++)
                {
                    var column =
                        new PartitionColumn(
                            this, i,
                            partitionsColId[i],
                            (i + 1 < partitionsColId.Length) ? partitionsColId[i + 1] - 1 : columnsWidth.Length - 1,
                            partitionsWidth[i]);
                    partitionColumns[i] = column;
                }

                #endregion
            }
        }


        /// <summary>
        /// Initialize the next partitionElement taking care of the partition order of the partitionGrid
        /// </summary>
        /// <param name="levelIndex">Index of the current partitionLevel</param>
        /// <param name="columnIndex">Index of the current partitionColumn</param>
        /// <returns>
        /// the next partition according to the partition order 
        /// and to given <paramref name="levelIndex"/> and <paramref name="columnIndex"/> parameters
        /// </returns>
        public PartitionElement NextElement(int levelIndex, int columnIndex)
        {
            switch (PartitionOrder)
            {
                case PartitionOrder.OrderByLevelFirst:
                    if (columnIndex + 1 >= PartitionColumnCount)
                    {
                        levelIndex++;
                        columnIndex = 0;
                    }
                    else columnIndex++;
                    break;
                case PartitionOrder.OrderByColumnFirst:
                    if (levelIndex + 1 >= PartitionLevelCount)
                    {
                        columnIndex++;
                        levelIndex = 0;
                    }
                    else levelIndex++;
                    break;
                default:
                    Debug.Assert(false, "chosen navigation mode not known by PartitionGrid");
                    break;
            }

            if (levelIndex >= partitionLevels.Length || columnIndex >= partitionColumns.Length)
                return null;

            return new PartitionElement(this, levelIndex, columnIndex);
        }

        /// <summary>
        /// Initialize the next partitionElement taking care of the partition order of the partitionGrid
        /// </summary>
        /// <param name="element">Current partitionElement</param>
        /// <returns>Return the partition coming after the given partition in the partitionGrid</returns>
        public PartitionElement NextElement(PartitionElement element)
        {
            return NextElement(element.LevelId, element.ColumnId);
        }


        /// <summary>
        /// Calculate the ordinal number of a partition which takes care about the defined partition order
        /// </summary>
        /// <param name="levelId">partitionLevel index of the partition</param>
        /// <param name="columnId">partitionColumn index of the partition</param>
        /// <returns>ordinal number of a partition</returns>
        public int GetPartitionOrdinal(int levelId, int columnId)
        {
            switch (PartitionOrder)
            {
                case PartitionOrder.OrderByLevelFirst:
                    return levelId*PartitionColumnCount + columnId;
                case PartitionOrder.OrderByColumnFirst:
                    return columnId*PartitionLevelCount + levelId;
                default:
                    return 0;
            }
        }

        ///<summary>
        /// Find the index of the <see cref="PartitionColumn"/> instance which contains the column indexed by <paramref name="columnId"/>
        ///</summary>
        ///<param name="columnId">The column index to search</param>
        ///<returns>The index of the <see cref="PartitionColumn"/> instance (-1 if not found)</returns>
        public int PartitionColumnIdFromColumnId(int columnId)
        {
            for (int pcolId = 0; pcolId < PartitionColumnCount; pcolId++)
            {
                if (PartitionColumn(pcolId).LastColumn < columnId) continue;

                if (PartitionColumn(pcolId).FirstColumn >= columnId)
                    return pcolId;
            }

            return -1;
        }

        ///<summary>
        /// Find the index of the <see cref="PartitionLevel"/> instance which contains the row indexed by <paramref name="rowId"/>
        ///</summary>
        ///<param name="rowId">The row index to search</param>
        ///<returns>The index of the <see cref="PartitionLevel"/> instance (-1 if not found)</returns>
        public int PartitionLevelIdFromRowId(int rowId)
        {
            for (int prowId = 0; prowId < PartitionColumnCount; prowId++)
            {
                if (PartitionLevel(prowId).LastRow < rowId) continue;

                if (PartitionLevel(prowId).FirstRow >= rowId)
                    return prowId;
            }

            return -1;
        }
    }
}